<?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed');

class Core_Db_Select {

	const DISTINCT       = 'distinct';
	const COLUMNS        = 'columns';
	const FROM           = 'from';
	const WHERE          = 'where';
	const GROUP          = 'group';
	const HAVING         = 'having';
	const ORDER          = 'order';
	const LIMIT          = 'limit';

	const INNER_JOIN     = 'inner join';
	const LEFT_JOIN      = 'left join';
	const RIGHT_JOIN     = 'right join';
	const FULL_JOIN      = 'full join';

	/**
	 * The component parts of a SELECT statement.
	 *
	 * @var array
	 */
	protected $_parts = array();

	/**
	 * The initial values for the $_parts array.
	 *
	 * @var array
	 */
	protected static $_partsInit = array(
		self::DISTINCT     => false,
		self::COLUMNS      => array(),
		self::FROM         => array(),
		self::WHERE        => array(),
		self::GROUP        => array(),
		self::HAVING       => array(),
		self::ORDER        => array(),
		self::LIMIT        => array()
	);

	public function __construct() {
		$this->_parts = self::$_partsInit;
	}

	public function getPart($part) {
		return $this->_parts[$part];
	}
	
	public function setPart($part, $val) {
		$this->_parts[$part] = $val;
		return $this;
	}
	
	public function resetPart($part) {
		if (isset(self::$_partsInit[$part]))
			$this->_parts[$part] = self::$_partsInit[$part];
		return $this;
	}

	/**
	 * Specifies the columns used in the FROM clause.
	 *
	 * @param  mixed The columns to select from this table.
	 * @return Entity_Select.
	 */
	public function columns($col) {
		if (is_string($col))
			array_push($this->_parts[self::COLUMNS], $col);
		else if (is_array($col))
			$this->_parts[self::COLUMNS] = array_merge($this->_parts[self::COLUMNS], $col);
		return $this;
	}

	public function group($col) {
		if (is_string($col))
			array_push($this->_parts[self::GROUP], $col);
		else if (is_array($col))
			$this->_parts[self::GROUP] = array_merge($this->_parts[self::GROUP], $col);
		return $this;
	}
	
	public function where($cond) {
		if (is_string($cond))
			array_push($this->_parts[self::WHERE], $cond);
		else
			$this->_parts[self::WHERE] = array_merge($this->_parts[self::WHERE], $cond);
		return $this;
	}

	public function having($cond) {
		if (is_string($cond))
			array_push($this->_parts[self::HAVING], $cond);
		else
			$this->_parts[self::HAVING] = array_merge($this->_parts[self::HAVING], $cond);
		return $this;
	}
	
	public function order($col, $asc=true) {
		$col .= $asc ? ' ASC' : ' DESC';
		array_push($this->_parts[self::ORDER], $col);
		return $this;
	}

	public function limit($count=1, $offset=0) {
		$this->_parts[self::LIMIT] = array('count' => $count, 'offset' => $offset);
		return $this;
	}
	
	public function distinct($flag = true) {
		$this->_parts[self::DISTINCT] = $flag;
		return $this;
	}
	
	protected function _join($type, $table, $cond, $cols) {
		$part = array(
			'joinType' => $type,
			'table' => $table,
			'condition' => $cond,
			'columns' => $cols
		);
		$from = $this->_parts[self::FROM];
		$from[$table] = $part;
		$this->setPart(self::FROM, $from);
		return $this;
	}
	
	public function from($table, $cols=array()) {
		return $this->_join(self::FROM, $table, null, $cols);
	}

	public function join($table, $cond=null, $cols=array()) {
		return $this->_join(self::INNER_JOIN, $table, $cond, $cols);
	}
	
	public function joinLeft($table, $cond=null, $cols=array()) {
		return $this->_join(self::LEFT_JOIN, $table, $cond, $cols);
	}
	
	public function joinRight($table, $cond=null, $cols=array()) {
		return $this->_join(self::RIGHT_JOIN, $table, $cond, $cols);
	}
	
	public function joinFull($table, $cond=null, $cols=array()) {
		return $this->_join(self::FULL_JOIN, $table, $cond, $cols);
	}
	
	public function __toString() {
		$sql = 'SELECT';
		foreach (array_keys(self::$_partsInit) as $part) {
			$method = '_render' . ucfirst($part);
			if (method_exists($this, $method)) {
				$sql = $this->$method($sql);
			}
		}
		return $sql;
	}
	
	protected function _renderColumns($sql) {
		// collect columns in column & from part
		$columns = $this->_parts[self::COLUMNS];
		foreach($this->_parts[self::FROM] as $from)
			if (!empty($from['columns']))
				array_push($columns, $from['columns']);

		$columns = array_unique($columns);
		if (empty($columns)) return $sql .= ' *';
		return $sql .= ' ' . implode(', ', $columns);
	}
	
	protected function _renderFrom($sql) {
		if (empty($this->_parts[self::FROM])) return '';

		$f = array();
		$j = '';
		foreach ($this->_parts[self::FROM] as $part) {
			if ($part['joinType'] == self::FROM) {
				$f[]= $part['table'];
			} else {
				$j .= ' ' . strtoupper($part['joinType']) . ' ' . $part['table'];
				if (!empty($part['condition']))
					$j .= ' ON ' . $part['condition'];
			}
		}
		
		if (!empty($from)) throw new Exception('Invalid FORM clause');
		
		$sql .= ' FROM ' . implode(', ', $f);
		$sql .= $j;
		return $sql;
	}
	
	protected function _renderWhere($sql) {
		if ($this->_parts[self::FROM] && $this->_parts[self::WHERE]) {
			$sql .= ' WHERE ' .  implode(' AND ', $this->_parts[self::WHERE]);
		}

		return $sql;
	}
	
	protected function _renderOrder($sql) {
		if ($this->_parts[self::ORDER]) {
			$sql .= ' ORDER BY ' . implode(', ', $this->_parts[self::ORDER]);
		}
		return $sql;
	}

	protected function _renderLimit($sql) {
		$limit = $this->_parts[self::LIMIT];
		if (!empty($limit)) {
			$sql .= " LIMIT {$limit['offset']}, {$limit['count']}";
		}
		return $sql;
	}
	
	protected function _renderGroup($sql) {
		$group = $this->_parts[self::GROUP];
		if (!empty($group)) {
			$sql .= ' GROUP BY ' . implode(', ', $group);
		}
		return $sql;
	}
	
	protected function _renderHaving($sql) {
		$having = $this->_parts[self::HAVING];
		if (!empty($having)) {
			$sql .= ' HAVING ' . implode(' AND', $having);
		}
		return $sql;
	}

	protected function _renderDistinct($sql) {
		if ($this->_parts[self::DISTINCT]) $sql .= ' DISTINCT';
		return $sql;
	}
	
}